#define _POSIX_SOURCE
#define _XOPEN_SOURCE
#include <pthread.h>
#include <sys/queue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>
#include <signal.h>
#include <sys/time.h>
#include "libmem.h"
#include "liblist.h"
#include "libqueue.h"
#include "defines.h"
#include "libtabla.h"
#include "liblog.h"
#include "libpcb.h"

#define MAXCMD 200

void cargarConfiguracion();
void inicializarListas();
void inicializarThreads();
int ejecutarPCP(int);
void terminarShrek(int);
void liberarRecursos();
void ejecutarPrograma(char*);
void mostrarAyuda();
void mostrarComandoNoEncontrado();
void insertarProcesoNuevo(pthread_t pidLTT);

void inicializarColaEntrada();
void insertProgramaColaEntrada(char *);
char* retirarEntradaCola();
int colaEntradaVacia();

//Funcion a llamar al recibir la señal USR1
void pedirSuspenderProcesoPST();
//Funcion a llamar al recibir la señal USR2
void logueoEstadoListas();

//Funcion a llamar al recibir la señal kill
void killMLP();

//Funciones para que chequeo del PST
void configAlarmPST();
void pstChequearEstado();

int consolaEntrada();

void fnLTT(); 	//funcion operatoria del thread LTT
void fnPRT();	//funcion operatoria del thread PRT
void fnPST();	//funcion operatoria del thread PST
void fnLOG();	//funcion operatoria del thread LOG

Lista* listaNuevos = NULL;
Lista* listaExSuspendidos = NULL;
Lista* listaSuspendidosListos = NULL;
Lista* listaListos = NULL;
Lista* listaBloqueados = NULL;
Lista* listaIOTerminada = NULL;
Tabla* tablaProcesos = NULL;

pthread_t thLTT;
pthread_t thPRT;
pthread_t thPST;
pthread_t thLOG;
	
int cantMPS;			//Máximo de procesos que se pueden iniciar en el sistema (MPS)
int cantMMP; 			//Máximo nivel de multiprogramacion (MMP)
int delaySegCheckSys; 	//Intervalo de tiempo de checkeo del estado del sistema
int cantMaxProcSus; 	//Cantidad máxima de procesos que se pueden suspender en un ciclo
int delaySegLTT;		//Tiempo en segundos que le insume al LTT llevar un proceso de una lista a otra
int delaySegPRT;		//Tiempo en segundos que le insume al PRT llevar un proceso de una lista a otra
char* pathProcSusp;		//Ruta del directorio donde se guardarán los procesos suspendidos

int actCantMPS;			//cantidad actual de procesos iniciados en el sistema (MPS)
int actCantMMP;			//nivel actual de multiprogramacion (MMP)

int pidPCP;

TAILQ_HEAD(tailhead, entry) colaEntrada;	//Declaro la cola de entrada

struct entry {
	char *programa;
	TAILQ_ENTRY(entry) entries;
};

int main(int argc, char **argv) {
	sigset_t signal_set;
	int mailbox;
	int comando = 0;

	//Bloqueo todas las señales
   	sigfillset( &signal_set );
	pthread_sigmask( SIG_BLOCK, &signal_set, NULL );
	
	//Inicializo la cola de entrada
	inicializarColaEntrada();

	printf("Inicializando Proceso MLP...\n");
	//Leo la configuración
	cargarConfiguracion();
	
	//Creo todas las listas y tabla en memoria compartida
	inicializarListas();

	//Creo un mailbox donde mandar y leer todos los mensajes del sistema
	mailbox = crearCola();

	//Lanzo los threads del MLP
	inicializarThreads();
	
	//Lanzo el proceso del PCP, le paso el id del mailbox
	printf("Inicializando Proceso PCP...\n");
	pidPCP = ejecutarPCP(mailbox);

	//Configuracion de la alarma para el chequeo del PST
	configAlarmPST();

	printf("Bienvenido a SHREK\n");
	//Espero la entrada de comandos
	while(comando != 1){
		comando = consolaEntrada();
	}

	//Mato al proceso PCP y finalizo todos mis threads
	terminarShrek(pidPCP);

	//Espero la finalización de los threads
	pthread_join(thLTT, NULL);
	pthread_join(thPRT, NULL);
	pthread_join(thPST, NULL);
	pthread_join(thLOG, NULL);	

	//Retiro de memoria todas las listas en memoria compartida y cualquier otra memoria reservada
	liberarRecursos();
	
	return EXIT_SUCCESS;
}

int ejecutarPCP(int int_mailbox){
	
	char *punt_sh_nuevos = (char*)reservar(20);
	char *punt_sh_exsusp = (char*)reservar(20);
	char *punt_sh_susplis = (char*)reservar(20);
	char *punt_sh_listos = (char*)reservar(20);
	char *punt_sh_bloq = (char*)reservar(20);
	char *punt_sh_ioterm = (char*)reservar(20);
	char *punt_sh_tabla = (char*)reservar(20);
	char *cant_sh_lista = (char*)reservar(20);
	char *id_mailbox = (char*)reservar(20);
	char *spidMLP = (char*)reservar(20);	
	
	/*Tengo que armar strings de la posición de memoria donde estan las listas compartidas para
	 * poder pasarlas por parametro, ya que no puedo pasar un int directamente */
	sprintf(punt_sh_nuevos, "%i", listaNuevos->_shmem);
	sprintf(punt_sh_exsusp, "%i", listaExSuspendidos->_shmem);
	sprintf(punt_sh_susplis, "%i", listaSuspendidosListos->_shmem);
	sprintf(punt_sh_listos, "%i", listaListos->_shmem);
	sprintf(punt_sh_bloq, "%i", listaBloqueados->_shmem);
	sprintf(punt_sh_ioterm, "%i", listaIOTerminada->_shmem);
	sprintf(punt_sh_tabla, "%i",tablaProcesos->_shmem);
	sprintf(spidMLP, "%i",getpid());
	
	
	/*Como se que todas las listas tiene el mismo tamaño maximo, en vez de pasar por cada lista su tamaño, 
	 * solo paso en un parametro el tamaño maximo a utilizar todas las listas */
	sprintf(cant_sh_lista, "%i", listaNuevos->_cantMaxElementos);
	sprintf(id_mailbox, "%i", int_mailbox);	
	
	//Creo un nuevo proceso y luego ejecuto el proceso PCP
	pidPCP = fork();
	if (pidPCP == 0) {
		execlp("pcp", "pcp", \
			punt_sh_nuevos, punt_sh_exsusp, \
			punt_sh_susplis, punt_sh_listos, \
			punt_sh_bloq, punt_sh_ioterm, punt_sh_tabla, \
			cant_sh_lista, id_mailbox, spidMLP, NULL);
	}
	
			
	liberar(punt_sh_nuevos);
	liberar(punt_sh_exsusp);
	liberar(punt_sh_susplis);
	liberar(punt_sh_listos);
	liberar(punt_sh_bloq);
	liberar(punt_sh_ioterm);
	liberar(cant_sh_lista);
	liberar(id_mailbox);
	liberar(spidMLP);
	
	return pidPCP;
}

void terminarShrek(int pidPCP){
	//Envio la señal para matar el PCP y no me importa nada. Hasta la vista baby..
	kill(pidPCP, SIGKILL);
	//Espero la finalizacion del proceso PCP
	waitpid(pidPCP, NULL, 0);
	//Cancelo todos los threads
	pthread_cancel(thLTT);
	pthread_cancel(thPRT);
	pthread_cancel(thPST);
	pthread_cancel(thLOG);
	
}

void inicializarListas(){
	int retsh;

	listaNuevos = (Lista*)reservar(sizeof(Lista));
	listaExSuspendidos = (Lista*)reservar(sizeof(Lista));
	listaSuspendidosListos = (Lista*)reservar(sizeof(Lista));
	listaListos = (Lista*)reservar(sizeof(Lista));
	listaBloqueados = (Lista*)reservar(sizeof(Lista));
	listaIOTerminada = (Lista*)reservar(sizeof(Lista));	
	tablaProcesos = (Tabla*)reservar(sizeof(Tabla));
		

	retsh = crearLista(listaNuevos, cantMPS);
	if(retsh == -1){
		perror("Error creando lista listaNuevos");
		exit(EXIT_FAILURE);
	}
	retsh = crearLista(listaExSuspendidos, cantMPS);
	if(retsh == -1){
		perror("Error creando lista listaExSuspendidos");
		exit(EXIT_FAILURE);
	}
	retsh = crearLista(listaSuspendidosListos, cantMPS);
	if(retsh == -1){
		perror("Error creando lista listaSuspendidosListos");
		exit(EXIT_FAILURE);
	}
	retsh = crearLista(listaListos, cantMPS);
	if(retsh == -1){
		perror("Error creando lista listaListos");
		exit(EXIT_FAILURE);
	}
	retsh = crearLista(listaBloqueados, cantMPS);
	if(retsh == -1){
		perror("Error creando lista listaBloqueados");
		exit(EXIT_FAILURE);
	}	
	retsh = crearLista(listaIOTerminada, cantMPS);
	if(retsh == -1){
		perror("Error creando lista listaIOTerminada");
		exit(EXIT_FAILURE);
	}
	retsh = crearTabla(tablaProcesos, cantMPS);
	if(retsh == -1){
		perror("Error creando la tabla de Procesos");
		exit(EXIT_FAILURE);
	}
}

void inicializarThreads(){
	
	int retth;
	//Inicializo el thread LTT
	retth = pthread_create(&thLTT, NULL, (void *)fnLTT, NULL);
	if(retth) {
		perror("Error iniciando thread LTT, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	//Inicializo el thread PRT
	retth = pthread_create(&thPRT, NULL, (void *)fnPRT, NULL);
	if(retth) {
		perror("Error iniciando thread PRT, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	//Inicializo el thread PST
	retth = pthread_create(&thPST, NULL, (void *)fnPST, NULL);
	if(retth) {
		perror("Error iniciando thread PST, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	//Inicializo el thread LOG
	retth = pthread_create(&thLOG, NULL, (void *)fnLOG, NULL);
	if(retth) {
		perror("Error iniciando thread LOG, ejecución abortada.");
		exit(EXIT_FAILURE);
	}	
}

void cargarConfiguracion(){

	
	pathProcSusp = (char*)reservar(MAXPATH+1);
	cantMPS = atoi(getenv("MPS")); 
	cantMMP = atoi(getenv("MMP"));
	delaySegCheckSys = atoi(getenv("TIME_CHEQ"));
	delaySegLTT = atoi(getenv("TIME_TRAVEL_LTT"));
	delaySegPRT = atoi(getenv("TIME_TRAVEL_PRT"));
	cantMaxProcSus = atoi(getenv("P_SUSPEND"));
	strcpy(pathProcSusp, getenv("PATH_SUSPEND"));
}

void liberarRecursos(){
	borrarLista(listaNuevos);
	borrarLista(listaExSuspendidos);
	borrarLista(listaSuspendidosListos);
	borrarLista(listaListos);
	borrarLista(listaBloqueados);
	borrarLista(listaIOTerminada);
	borrarTabla(tablaProcesos);

	destruirCola();
	
	liberar(listaNuevos);
	liberar(listaExSuspendidos);
	liberar(listaSuspendidosListos);
	liberar(listaListos);
	liberar(listaBloqueados);
	liberar(listaIOTerminada);	
		
	liberar(pathProcSusp);	
}

int consolaEntrada(){
	int ret = 0;
	char *input = (char*)reservar(MAXCMD+1);
	char *comando, *parametro;
	
	/*Muestro el promp y espero que ingrese un comando*/
	printf("SHREK>");
	fgets(input, MAXCMD, stdin);
	//Me fijo que se haya ingresado algo
	if (strlen(input) > 1){
		comando = strtok(input, " \n");
		/*Listado de posibles comandos*/
		if(strcmp(comando,"exit") == 0) {
			ret = 1;
		}else if(strncmp(comando, "ejecutar", strlen("ejecutar")) == 0) {
			parametro = strtok(NULL, " \n");
			if (parametro != NULL) {
				if(fileExist(parametro)) {
					ejecutarPrograma(parametro);
				}
				else {
					printf("El programa no existe\n");
				}
			}
			ret = 0;
		}else if(strcmp(comando,"help") == 0) {
			mostrarAyuda();		
			ret = 0;		
		}else if(strncmp(comando, "TIME_CHEQ", strlen("TIME_CHEQ")) == 0) {
			parametro = strtok(NULL, " \n");
			if (parametro != NULL)
				delaySegCheckSys = atoi(parametro);
			ret = 0;
		}else if(strncmp(comando, "TIME_TRAVEL_LTT", strlen("TIME_TRAVEL_LTT")) == 0) {
			parametro = strtok(NULL, " \n");
			if (parametro != NULL)
				delaySegLTT = atoi(parametro);
			ret = 0;
		}else if(strncmp(comando, "TIME_TRAVEL_PRT", strlen("TIME_TRAVEL_PRT")) == 0) {
			parametro = strtok(NULL, " \n");
			if (parametro != NULL)
				delaySegPRT = atoi(parametro);
			ret = 0;
		}else if(strncmp(comando, "P_SUSPEND", strlen("P_SUSPEND")) == 0) {
			parametro = strtok(NULL, " \n");
			if (parametro != NULL)
				cantMaxProcSus = atoi(parametro);						
			ret = 0;	
		}else if(strncmp(comando, "TIME_CPU", strlen("TIME_CPU")) == 0) {
			parametro = strtok(NULL, " \n");
			if (parametro != NULL)
				enviarMsj(PCP, CHANGE_TIME_CPU, 0, NULL);
			ret = 0;
		}else if(strncmp(comando, "TIME_IO", strlen("TIME_IO")) == 0) {
			parametro = strtok(NULL, " \n");
			if (parametro != NULL)
				enviarMsj(PCP, CHANGE_TIME_IO, 0, NULL);
			ret = 0;
		}else if(strncmp(comando, "TIME_TRAVEL_STT", strlen("TIME_TRAVEL_STT")) == 0) {
			parametro = strtok(NULL, " \n");
			if (parametro != NULL)
				enviarMsj(PCP, CHANGE_TIME_TRAVEL_STT, 0, NULL);			
			ret = 0;									
		}else{
			mostrarComandoNoEncontrado();
			ret = 0;
		}
	}
	
	liberar(input);
	return ret;
}

void ejecutarPrograma(char* parametro){
	
	insertProgramaColaEntrada(parametro);
	enviarMsj(LTT, ACEPTAR_PROC, 0, NULL);
}

void mostrarAyuda(){
	printf("Los comandos aceptados son:\n");
	printf("	help: Muestra esta página de ayuda\n");
	printf("	exit: Termina el sistema SHREK\n");
	printf("	ejecutar <programa>: Inicializa la ejecución del programa indicado\n");
	printf("Por mas ayuda contacte a su administrador de sistemas mas cercano\n");	
}

void mostrarComandoNoEncontrado(){
	printf("Comando no encontrado, para ver la ayuda de SHREK tipeo <help>\n");	
}

void insertarProcesoNuevo(pthread_t pidLTT) {
	char *path = (char*)reservar(MAXCMD+1);
	int pid;
	
	path = retirarEntradaCola();
	//Paso el proceso a la lista de Nuevos
	lockLista(listaNuevos);
		pid = insertarProceso(tablaProcesos, path);	//inserto el proceso en la tabla
		cambiarEstado(tablaProcesos, pid, NUEVO);	//le cambio el estado del proceso
		sleep(delaySegLTT);	//Cantidad de tiempo que le insume al LTT mover elementos a la lista
		insertarElemento(listaNuevos, pid);
		actCantMPS++;	//Incremento el valor del MPS
		actCantMMP++;	//Incremento el valor del MMP
	unlockLista(listaNuevos);
	loguear_change_list(pid, ENTRADA, NUEVO, tablaProcesos, "MLP", pidLTT, "LTT", "INFO");
	//Le aviso al STT que hay un proceso listo en la lista de Nuevos
	enviarMsj(STT, PROC_DISPONIBLE, 0, NULL);
	liberar(path);
}

void fnLTT(){
	infoMsj mensaje;

	pthread_t pidLTT = pthread_self();
	//Variable para chequear si hay encolados en la cola Entrada
	int encolados = 0;
	int exitLoop= 0;
	
	while(1){		
		mensaje = recibirMsj(LTT);
		//Me avisan que puedo ingresar un programa nuevo
		if (mensaje.action == ACEPTAR_PROC) {
			//Verifico que no se superen los limites permitidos
			if (actCantMPS < cantMPS && actCantMMP < cantMMP) {
				//Verifico que la cola de entrada no este vacia
				if (!colaEntradaVacia()) {
					insertarProcesoNuevo(pidLTT);
				}
			}
			else {
				encolados++;
			}
		}
		else if (mensaje.action == SYSUPDATE){
			exitLoop=0;
			//Sacar todos los encolados hasta que se alcance el limite de MMP o MPS
			while(encolados > 0 && !exitLoop) {
				if (actCantMPS < cantMPS && actCantMMP < cantMMP) {
				//Verifico que la cola de entrada no este vacia
					if (!colaEntradaVacia()) {
						insertarProcesoNuevo(pidLTT);
						encolados--;
					}
				} else {
					exitLoop = 1;
				}
			}
			
		}
	}
}

void fnPRT(){
	infoMsj mensaje;
	int pid;
	pthread_t pidPRT = pthread_self();
	//Encolados que no pudieron entrar a causa del MMP
	int encolados = 0;
	
	while(1){
		mensaje = recibirMsj(PRT);
		if (mensaje.action == PROC_DISPONIBLE_SUSPLIST){
			if (actCantMMP < cantMMP){
				//Retiro el proceso de la lista de suspendidosListos
				lockLista(listaSuspendidosListos);
					pid = retirarElemento(listaSuspendidosListos);
				unlockLista(listaSuspendidosListos);
				//Muevo el proceso a la lista de ExSuspendidos
				lockLista(listaExSuspendidos);
					sleep(delaySegPRT);
					insertarElemento(listaExSuspendidos, pid);
					actCantMMP++;	//Incremento el valor del MMP
				unlockLista(listaExSuspendidos);
				loguear_change_list(pid, SUSPLISTO, EXSUSP, tablaProcesos, "MLP", pidPRT, "PRT", "INFO");
				//Le aviso al STT que hay un proceso listo en alguna de las 3 listas que él verifica
				enviarMsj(STT, PROC_DISPONIBLE, 0, NULL);
			}
			else {
				//Si no puede entrar queda encolado
				encolados++;
			}
		} if (mensaje.action == SYSUPDATE) {
			while(encolados > 0 && actCantMMP < cantMMP ) {
				//Retiro el proceso de la lista de suspendidosListos
				lockLista(listaSuspendidosListos);
				pid = retirarElemento(listaSuspendidosListos);
				unlockLista(listaSuspendidosListos);
				//Muevo el proceso a la lista de ExSuspendidos
				lockLista(listaExSuspendidos);
				sleep(delaySegPRT);
				insertarElemento(listaExSuspendidos, pid);
				actCantMMP++;	//Incremento el valor del MMP
				unlockLista(listaExSuspendidos);
				loguear_change_list(pid, SUSPLISTO, EXSUSP, tablaProcesos, "MLP", pidPRT, "PRT", "INFO");
				//Le aviso al STT que hay un proceso listo en alguna de las 3 listas que él verifica
				enviarMsj(STT, PROC_DISPONIBLE, 0, NULL);
				//Decremento encolado
				encolados--;
			}
		}
	}
}

void fnPST(){
	infoMsj mensaje;
	
	while(1){
		mensaje = recibirMsj(PST);
		if (mensaje.action == SUSPENDER_PROC){
			enviarMsj(PCP, SUSPENDER_PROC, 0, "1");
			//El decremento del MMP lo lleva a cabo el PCP para asegurase que se suspendió un proceso
		}
		else if (mensaje.action == CHEQUEAR_ESTADO){
			if (actCantMPS < cantMPS && actCantMMP == cantMMP && cantidadElementos(listaListos) == 0){
				char *sCantMaxProcSus = (char*)reservar(20);	
				sprintf(sCantMaxProcSus, "%i", cantMaxProcSus);
				enviarMsj(PCP, SUSPENDER_PROC, 0, sCantMaxProcSus);
				liberar(sCantMaxProcSus);
			}
		}
		else if (mensaje.action == PST_INCREMENTAR_MPS){
			actCantMPS++;
		}
		else if (mensaje.action == PST_INCREMENTAR_MMP){
			actCantMMP++;
		}
		else if (mensaje.action == PST_DECREMENTAR_MPS){
			actCantMPS--;
			enviarMsj(LTT, SYSUPDATE, 0, NULL);
			enviarMsj(PRT, SYSUPDATE, 0, NULL);
		}
		else if (mensaje.action == PST_DECREMENTAR_MMP){
			actCantMMP--;
			enviarMsj(LTT, SYSUPDATE, 0, NULL);
			enviarMsj(PRT, SYSUPDATE, 0, NULL);
		}		
	}
}

void fnLOG(){
	sigset_t signal_set;
	int sig;
	
    while(1){
		/* espero por una señal */
		sigfillset( &signal_set );
		sigwait( &signal_set, &sig );

		switch( sig ){
			case SIGUSR1:
				pedirSuspenderProcesoPST();
		  		break;			
			case SIGUSR2:
				logueoEstadoListas();
		  		break;
			case SIGALRM:
				pstChequearEstado();
		  		break;
		  	case SIGKILL:
		  		killMLP();
		  		break;
		}
    }
}

void configAlarmPST(){
	/* Estructura con el contador de tiempo */
	struct itimerval contador;
	/* Valor inicial del contador */
	struct timeval tiempoInicial;
	/* Tiempo de repetición del contador */
	struct timeval tiempoRepeticion;

	/* Tiempo inicial del contador = tiempo configurado */
	tiempoInicial.tv_sec = delaySegCheckSys;
	tiempoInicial.tv_usec = 0;

	/* Ttiempo de repetición = tiempo configurado */
	tiempoRepeticion.tv_sec = delaySegCheckSys;
	tiempoRepeticion.tv_usec = 0;

	/* Se rellenan los datos del contador */
	contador.it_value = tiempoInicial;
	contador.it_interval = tiempoRepeticion;

	setitimer (ITIMER_REAL, &contador, NULL);
}


void pedirSuspenderProcesoPST(){
	enviarMsj(PST, SUSPENDER_PROC, 0, NULL);
}

void pstChequearEstado(){
	//Mensaje al PST para que cheque el estado
	enviarMsj(PST, CHEQUEAR_ESTADO, 0, NULL);
	/*Cargo de vuelta la configuracion de la alarma por 
	si se modifico el intervalo en tiempo de ejecucion*/
	configAlarmPST();
}

void logueoEstadoListas(){
	loguearMLP ( listaNuevos, listaBloqueados, listaIOTerminada, tablaProcesos, "INFO");
	enviarMsj(PCP, LOG_STATE, 0, NULL);
	
}

void inicializarColaEntrada(){
     TAILQ_INIT(&colaEntrada);
}

void insertProgramaColaEntrada(char *programa){
	struct entry *entrada;
	entrada = (struct entry *)reservar(sizeof(struct entry));
	entrada->programa = (char *)reservar(strlen(programa)*sizeof(char)+1);
	strcpy(entrada->programa, programa);
	TAILQ_INSERT_TAIL(&colaEntrada, entrada, entries);
}

char* retirarEntradaCola(){
	char *aux = (char*)reservar(strlen(colaEntrada.tqh_first->programa));
	strcpy(aux, colaEntrada.tqh_first->programa);
	TAILQ_REMOVE(&colaEntrada, colaEntrada.tqh_first, entries);
	return aux;
}

int colaEntradaVacia(){
	int ret = 1;
	if(colaEntrada.tqh_first != NULL)
		ret = 0;
	else
		ret = 1;

	return ret;
}

void killMLP() {
	terminarShrek(pidPCP);
	liberarRecursos();
	kill(getpid(),SIGKILL);
}
